package com.edu.learning.concurrent.sample.mode4;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * DelayQueue是一个延时优先级阻塞队列。队列中只能存入Delayed接口实现的对象
 * DelayQueue中存入的对象要同时实现getDelay和compareTo两个方法
 * getDelay方法是用来检测队列中的元素是否到期；compareTo方法是用来给队列中的元素进行排序。
 *
 * DelayQueue持有一个PriorityBlockingQueue，每个Delayed对象实际上都放入了这个队列，并按照compareTo方法进行排序。
 * 当队列中对象的getDelay方法返回的值小于等于0（即对象已经超时）时，才可以将对象从队列中取出。
 * 若使用take方法，则方法会一直阻塞，直到队列头部的对象超时被取出；若使用poll方法，则当没有超时对象时，直接返回null。
 *
 * 总结来说，有如下几个特点：
 * 1. 队列中的对象都是Delayed对象，它实现了getDelay和compareTo两个方法；
 * 2. 队列中的对象按照优先级（按照compareTo）进行了排序，队列头部是最先超时的对象；
 * 3. take方法会在没有超时对象时一直阻塞，直到有对象超时；poll方法会在没有超时对象时返回null。
 * 4. 队列中不允许存储null，且iterator方法返回的值不能确保按顺序排列。
 *
 * DelayQueue通过PriorityQueue，使得超时的对象最先被处理，将take对象的操作阻塞住，避免了遍历方式的轮询，提高了性能。
 * 在很多需要回收超时对象的场景都能用上
 * @author ll
 * @Date 2017/10/26 14:29
 */
public class DelayQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        DelayQueue<DelayElement> queue = new DelayQueue<DelayElement>();
        for (int i = 0; i < 10; i ++) {
            queue.put(new DelayElement(1000 * i, "DelayElement " + i));
        }
        while (!queue.isEmpty()) {
            DelayElement delayElement = queue.take();
            System.out.println(delayElement.getName());
        }
    }

    static class DelayElement implements Delayed {
        private final long delay;
        private long expired;
        private final String name;

        public DelayElement(int delay, String name) {
            this.delay = delay;
            this.name = name;
            expired = System.currentTimeMillis() + delay;
        }

        public String getName() {
            return name;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expired - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            long d = getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
            return d == 0 ? 0 : ((d < 0) ? -1 : 1);
        }
    }
}
